 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.forms.editor;

 import java.util.Vector ;

 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.jface.dialogs.IPageChangeProvider;
 import org.eclipse.jface.dialogs.IPageChangedListener;
 import org.eclipse.jface.dialogs.PageChangedEvent;
 import org.eclipse.jface.util.SafeRunnable;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IEditorActionBarContributor;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IEditorSite;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.forms.IManagedForm;
 import org.eclipse.ui.forms.widgets.FormToolkit;
 import org.eclipse.ui.part.MultiPageEditorActionBarContributor;
 import org.eclipse.ui.part.MultiPageEditorPart;
 import org.eclipse.ui.part.MultiPageSelectionProvider;

 /**
  * This class forms a base of multi-page form editors that typically use one or
  * more pages with forms and one page for raw source of the editor input.
  * <p>
  * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does
  * not cause the page control to be created. Page control is created when an
  * attempt is made to select the page in question. This allows editors with
  * several tabs and complex pages to open quickly.
  * <p>
  * Subclasses should extend this class and implement <code>addPages</code>
  * method. One of the two <code>addPage</code> methods should be called to
  * contribute pages to the editor. One adds complete (standalone) editors as
  * nested tabs. These editors will be created right away and will be hooked so
  * that key bindings, selection service etc. is compatible with the one for the
  * standalone case. The other method adds classes that implement
  * <code>IFormPage</code> interface. These pages will be created lazily and
  * they will share the common key binding and selection service. Since 3.1,
  * FormEditor is a page change provider. It allows listeners to attach to it and
  * get notified when pages are changed. This new API in JFace allows dynamic
  * help to update on page changes.
  *
  * @since 3.0
  */
 public abstract class FormEditor extends MultiPageEditorPart implements
         IPageChangeProvider {

     /**
      * An array of pages currently in the editor. Page objects are not limited
      * to those that implement <code>IFormPage</code>, hence the size of this
      * array matches the number of pages as viewed by the user.
      * <p>
      * Subclasses can access this field but should not modify it.
      */
     protected Vector pages = new Vector ();

     private FormToolkit toolkit;

     private int currentPage = -1;

     private ListenerList pageListeners = new ListenerList();

     private static class FormEditorSelectionProvider extends
             MultiPageSelectionProvider {
         private ISelection globalSelection;

         /**
          * @param formEditor the editor
          */
         public FormEditorSelectionProvider(FormEditor formEditor) {
             super(formEditor);
         }

         public ISelection getSelection() {
             IEditorPart activeEditor = ((FormEditor) getMultiPageEditor())
                     .getActiveEditor();
             if (activeEditor != null) {
                 ISelectionProvider selectionProvider = activeEditor.getSite()
                         .getSelectionProvider();
                 if (selectionProvider != null)
                     return selectionProvider.getSelection();
             }
             if (globalSelection != null) {
                 return globalSelection;
             }
             return StructuredSelection.EMPTY;
         }

         /*
          * (non-Javadoc) Method declared on <code> ISelectionProvider </code> .
          */
         public void setSelection(ISelection selection) {
             IEditorPart activeEditor = ((FormEditor) getMultiPageEditor())
                     .getActiveEditor();
             if (activeEditor != null) {
                 ISelectionProvider selectionProvider = activeEditor.getSite()
                         .getSelectionProvider();
                 if (selectionProvider != null)
                     selectionProvider.setSelection(selection);
             } else {
                 this.globalSelection = selection;
                 fireSelectionChanged(new SelectionChangedEvent(this,
                         globalSelection));
             }
         }
     }

     /**
      * The constructor.
      */
     public FormEditor() {
     }

     /**
      * Overrides super to plug in a different selection provider.
      */
     public void init(IEditorSite site, IEditorInput input)
             throws PartInitException {
         setSite(site);
         setInput(input);
         site.setSelectionProvider(new FormEditorSelectionProvider(this));
     }

     /**
      * Creates the common toolkit for this editor and adds pages to the editor.
      *
      * @see #addPages
      */
     protected void createPages() {
         addPages();
     }

     /*
      * @see org.eclipse.ui.part.MultiPageEditorPart#createPageContainer(org.eclipse.swt.widgets.Composite)
      */
     protected Composite createPageContainer(Composite parent) {
         parent = super.createPageContainer(parent);
         toolkit = createToolkit(parent.getDisplay());
         return parent;
     }

     /**
      * Creates the form toolkit. The method can be implemented to substitute a
      * subclass of the toolkit that should be used for this editor. A typical
      * use of this method would be to create the form toolkit using one shared
      * <code>FormColors</code> object to share resources across the multiple
      * editor instances.
      *
      * @param display
      * the display to use when creating the toolkit
      * @return the newly created toolkit instance
      */
     protected FormToolkit createToolkit(Display display) {
         return new FormToolkit(display);
     }

     /**
      * Subclass should implement this method to add pages to the editor using
      * 'addPage(IFormPage)' method.
      */
     protected abstract void addPages();

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
      */
     public void addPageChangedListener(IPageChangedListener listener) {
         pageListeners.add(listener);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
      */
     public void removePageChangedListener(IPageChangedListener listener) {
         pageListeners.remove(listener);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.dialogs.IPageChangeProvider#getSelectedPage()
      */
     public Object getSelectedPage() {
         return getActivePageInstance();
     }

     /**
      * Adds the form page to this editor. Form page will be loaded lazily. Its
      * part control will not be created until it is activated for the first
      * time.
      *
      * @param page
      * the form page to add
      */
     public int addPage(IFormPage page) throws PartInitException {
         int i = super.addPage(page.getPartControl());
         configurePage(i, page);
         return i;
     }

     /**
      * Adds the form page to this editor at the specified index (0-based). Form
      * page will be loaded lazily. Its part control will not be created until it
      * is activated for the first time.
      *
      * @param index
      * the position to add the page at (0-based)
      * @param page
      * the form page to add
      * @since 3.1
      */
     public void addPage(int index, IFormPage page) throws PartInitException {
         super.addPage(index, page.getPartControl());
         configurePage(index, page);
     }

     /**
      * Adds a simple SWT control as a page. Overrides superclass implementation
      * to keep track of pages.
      *
      * @param control
      * the page control to add
      * @return the 0-based index of the newly added page
      */
     public int addPage(Control control) {
         int i = super.addPage(control);
         try {
             registerPage(-1, control);
         } catch (PartInitException e) {
             // cannot happen for controls
 }
         return i;
     }

     /**
      * Adds a simple SWT control as a page. Overrides superclass implementation
      * to keep track of pages.
      *
      * @param control
      * the page control to add
      * @param index
      * the index at which to add the page (0-based)
      * @since 3.1
      */
     public void addPage(int index, Control control) {
         super.addPage(index, control);
         try {
             registerPage(index, control);
         } catch (PartInitException e) {
             // cannot happen for controls
 }
     }

     /**
      * Tests whether the editor is dirty by checking all the pages that
      * implement <code>IFormPage</code>. If none of them is dirty, the method
      * delegates further processing to <code>super.isDirty()</code>.
      *
      * @return <code>true</code> if any of the pages in the editor are dirty,
      * <code>false</code> otherwise.
      * @since 3.1
      */

     public boolean isDirty() {
         if (pages != null) {
             for (int i = 0; i < pages.size(); i++) {
                 Object page = pages.get(i);
                 if (page instanceof IFormPage) {
                     IFormPage fpage = (IFormPage) page;
                     if (fpage.isDirty())
                         return true;
                 }
             }
         }
         return super.isDirty();
     }
     
     /**
      * Commits all dirty pages in the editor. This method should
      * be called as a first step of a 'save' operation.
      * @param onSave <code>true</code> if commit is performed as part
      * of the 'save' operation, <code>false</code> otherwise.
      * @since 3.3
      */

     protected void commitPages(boolean onSave) {
         if (pages != null) {
             for (int i = 0; i < pages.size(); i++) {
                 Object page = (IFormPage)pages.get(i);
                 if (page instanceof IFormPage) {
                     IFormPage fpage = (IFormPage)page;
                     IManagedForm mform = fpage.getManagedForm();
                     if (mform != null && mform.isDirty())
                         mform.commit(onSave);
                 }
             }
         }
     }

     /**
      * Adds a complete editor part to the multi-page editor.
      *
      * @see MultiPageEditorPart#addPage(IEditorPart, IEditorInput)
      */
     public int addPage(IEditorPart editor, IEditorInput input)
             throws PartInitException {
         int index = super.addPage(editor, input);
         if (editor instanceof IFormPage)
             configurePage(index, (IFormPage) editor);
         else
             registerPage(-1, editor);
         return index;
     }

     /**
      * Adds a complete editor part to the multi-page editor at the specified
      * position.
      *
      * @see MultiPageEditorPart#addPage(int, IEditorPart, IEditorInput)
      * @since 3.1
      */
     public void addPage(int index, IEditorPart editor, IEditorInput input)
             throws PartInitException {
         super.addPage(index, editor, input);
         if (editor instanceof IFormPage)
             configurePage(index, (IFormPage) editor);
         else
             registerPage(index, editor);
     }

     /**
      * Configures the form page.
      *
      * @param index
      * the page index
      * @param page
      * the page to configure
      * @throws PartInitException
      * if there are problems in configuring the page
      */
     protected void configurePage(int index, IFormPage page)
             throws PartInitException {
         setPageText(index, page.getTitle());
         // setPageImage(index, page.getTitleImage());
 page.setIndex(index);
         registerPage(index, page);
     }

     /**
      * Overrides the superclass to remove the page from the page table.
      *
      * @param pageIndex
      * the 0-based index of the page in the editor
      */
     public void removePage(int pageIndex) {
         if (pageIndex >= 0 && pageIndex < pages.size()) {
             Object page = pages.get(pageIndex);
             pages.remove(page);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) page;
                 if (!fpage.isEditor())
                     fpage.dispose();
                 updatePageIndices();
             }
         }
         super.removePage(pageIndex);
     }

     // fix the page indices after the removal
 private void updatePageIndices() {
         for (int i = 0; i < pages.size(); i++) {
             Object page = pages.get(i);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) page;
                 fpage.setIndex(i);
             }
         }
     }

     /**
      * Called to indicate that the editor has been made dirty or the changes
      * have been saved.
      */
     public void editorDirtyStateChanged() {
         firePropertyChange(PROP_DIRTY);
     }

     /**
      * Disposes the pages and the toolkit after disposing the editor itself.
      * Subclasses must call 'super' when reimplementing the method.
      */
     public void dispose() {
         super.dispose();
         for (int i = 0; i < pages.size(); i++) {
             Object page = pages.get(i);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) page;
                 // don't dispose source pages because they will
 // be disposed as nested editors by the superclass
 if (!fpage.isEditor())
                     fpage.dispose();
             }
         }
         pages = null;
         // toolkit may be null if editor has been instantiated
 // but never created - see defect #62190
 if (toolkit != null) {
             toolkit.dispose();
             toolkit = null;
         }
     }

     /**
      * Returns the toolkit owned by this editor.
      *
      * @return the toolkit object
      */
     public FormToolkit getToolkit() {
         return toolkit;
     }

     /**
      * Widens the visibility of the method in the superclass.
      *
      * @return the active nested editor
      */
     public IEditorPart getActiveEditor() {
         return super.getActiveEditor();
     }

     /**
      * Returns the current page index. The value is identical to the value of
      * 'getActivePage()' except during the page switch, when this method still
      * has the old active page index.
      * <p>
      * Another important difference is during the editor closing. When the tab
      * folder is disposed, 'getActivePage()' will return -1, while this method
      * will still return the last active page.
      *
      * @see #getActivePage
      * @return the currently selected page or -1 if no page is currently
      * selected
      */
     protected int getCurrentPage() {
         return currentPage;
     }

     /**
      * @see MultiPageEditorPart#pageChange(int)
      */
     protected void pageChange(int newPageIndex) {
         // fix for windows handles
 int oldPageIndex = getCurrentPage();
         if (oldPageIndex != -1 && pages.size() > oldPageIndex
                 && pages.get(oldPageIndex) instanceof IFormPage
                 && oldPageIndex != newPageIndex) {
             // Check the old page
 IFormPage oldFormPage = (IFormPage) pages.get(oldPageIndex);
             if (oldFormPage.canLeaveThePage() == false) {
                 setActivePage(oldPageIndex);
                 return;
             }
         }
         // Now is the absolute last moment to create the page control.
 Object page = pages.get(newPageIndex);
         if (page instanceof IFormPage) {
             IFormPage fpage = (IFormPage) page;
             if (fpage.getPartControl() == null) {
                 fpage.createPartControl(getContainer());
                 setControl(newPageIndex, fpage.getPartControl());
                 fpage.getPartControl().setMenu(getContainer().getMenu());
             }
         }
         if (oldPageIndex != -1 && pages.size() > oldPageIndex
                 && pages.get(oldPageIndex) instanceof IFormPage) {
             // Commit old page before activating the new one
 IFormPage oldFormPage = (IFormPage) pages.get(oldPageIndex);
             IManagedForm mform = oldFormPage.getManagedForm();
             if (mform != null)
                 mform.commit(false);
         }
         if (pages.size() > newPageIndex
                 && pages.get(newPageIndex) instanceof IFormPage)
             ((IFormPage) pages.get(newPageIndex)).setActive(true);
         if (oldPageIndex != -1 && pages.size() > oldPageIndex
                 && newPageIndex != oldPageIndex &&
                 pages.get(oldPageIndex) instanceof IFormPage)
             ((IFormPage) pages.get(oldPageIndex)).setActive(false);
         // Call super - this will cause pages to switch
 super.pageChange(newPageIndex);
         this.currentPage = newPageIndex;
         IFormPage newPage = getActivePageInstance();
         if (newPage != null)
             firePageChanged(new PageChangedEvent(this, newPage));
     }

     /**
      * Sets the active page using the unique page identifier.
      *
      * @param pageId
      * the id of the page to switch to
      * @return page that was set active or <samp>null </samp> if not found.
      */
     public IFormPage setActivePage(String pageId) {
         for (int i = 0; i < pages.size(); i++) {
             Object page = pages.get(i);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) page;
                 if (fpage.getId().equals(pageId)) {
                     setActivePage(i);
                     return fpage;
                 }
             }
         }
         return null;
     }

     /**
      * Finds the page instance that has the provided id.
      *
      * @param pageId
      * the id of the page to find
      * @return page with the matching id or <code>null</code> if not found.
      */
     public IFormPage findPage(String pageId) {
         for (int i = 0; i < pages.size(); i++) {
             Object page = pages.get(i);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) pages.get(i);
                 if (fpage.getId().equals(pageId))
                     return fpage;
             }
         }
         return null;
     }

     /**
      * Sets the active page using the unique page identifier and sets its input
      * to the provided object.
      *
      * @param pageId
      * the id of the page to switch to
      * @param pageInput
      * the page input
      * @return page that was set active or <samp>null </samp> if not found.
      */
     public IFormPage setActivePage(String pageId, Object pageInput) {
         IFormPage page = setActivePage(pageId);
         if (page != null) {
             IManagedForm mform = page.getManagedForm();
             if (mform != null)
                 mform.setInput(pageInput);
         }
         return page;
     }

     /**
      * Iterates through the pages calling similar method until a page is found
      * that contains the desired page input.
      *
      * @param pageInput
      * the object to select and reveal
      * @return the page that accepted the request or <code>null</code> if no
      * page has the desired object.
      * @see #setActivePage(String, Object)
      */
     public IFormPage selectReveal(Object pageInput) {
         for (int i = 0; i < pages.size(); i++) {
             Object page = pages.get(i);
             if (page instanceof IFormPage) {
                 IFormPage fpage = (IFormPage) page;
                 if (fpage.selectReveal(pageInput))
                     return fpage;
             }
         }
         return null;
     }

     /**
      * Returns active page instance if the currently selected page index is not
      * -1, or <code>null</code> if it is.
      *
      * @return active page instance if selected, or <code>null</code> if no
      * page is currently active.
      */
     public IFormPage getActivePageInstance() {
         int index = getActivePage();
         if (index != -1) {
             Object page = pages.get(index);
             if (page instanceof IFormPage)
                 return (IFormPage) page;
         }
         return null;
     }

     /**
      * @see MultiPageEditorPart#setActivePage(int)
      */
     protected void setActivePage(int pageIndex) {
         // fix for window handles problem
 // this should be called only when the editor is first opened
 if (pages.size() > pageIndex
                 && pages.get(pageIndex) instanceof IFormPage) {
             pageChange(pageIndex);
             IFormPage activePage = (IFormPage) pages.get(pageIndex);
             activePage.setActive(true);
             super.setActivePage(pageIndex);
         } else
             super.setActivePage(pageIndex);
         updateActionBarContributor(pageIndex);
     }

     /**
      * Notifies action bar contributor about page change.
      *
      * @param pageIndex
      * the index of the new page
      */
     protected void updateActionBarContributor(int pageIndex) {
         // this is to enable the undo/redo actions before a page change has
 // occurred
 IEditorActionBarContributor contributor = getEditorSite()
                 .getActionBarContributor();
         if (contributor != null
                 && contributor instanceof MultiPageEditorActionBarContributor) {
             ((MultiPageEditorActionBarContributor) contributor)
                     .setActivePage(getEditor(pageIndex));
         }
     }

     /**
      * Closes the editor programmatically.
      *
      * @param save
      * if <code>true</code>, the content should be saved before
      * closing.
      */
     public void close(final boolean save) {
         Display display = getSite().getShell().getDisplay();
         display.asyncExec(new Runnable () {
             public void run() {
                 if (toolkit != null) {
                     getSite().getPage().closeEditor(FormEditor.this, save);
                 }
             }
         });
     }

     private void registerPage(int index, Object page) throws PartInitException {
         if (!pages.contains(page)) {
             if (index == -1)
                 pages.add(page);
             else
                 pages.add(index, page);
         }
         if (page instanceof IFormPage) {
             IFormPage fpage = (IFormPage) page;
             if (fpage.isEditor() == false)
                 fpage.init(getEditorSite(), getEditorInput());
         }
     }

     private void firePageChanged(final PageChangedEvent event) {
         Object [] listeners = pageListeners.getListeners();
         for (int i = 0; i < listeners.length; ++i) {
             final IPageChangedListener l = (IPageChangedListener) listeners[i];
             SafeRunnable.run(new SafeRunnable() {
                 public void run() {
                     l.pageChanged(event);
                 }
             });
         }
     }
 }

